package com.leyou.search.service.impl;

import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.BrandDTO;
import com.leyou.item.dto.CategoryDTO;
import com.leyou.item.dto.SpecParamDTO;
import com.leyou.item.dto.SpuDTO;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.IGoodsService;
import com.leyou.search.service.ISearchService;
import com.leyou.test.enums.ExceptionEnum;
import com.leyou.test.exceptions.LyException;
import com.leyou.utils.BeanHelper;
import com.leyou.vo.PageResult;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SourceFilter;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Array;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements ISearchService {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private IGoodsService goodsService;

    @Autowired
    private GoodsRepository goodsRepository;



    /**
     * 基本分页搜索商品
     *
     * @param searchRequest
     * @return
     */
    @Override
    public PageResult<GoodsDTO> search(SearchRequest searchRequest) {
        //1.判断搜索条件key是否为空,为空,直接抛出异常
        String key = searchRequest.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }

        //2.创建原生搜索构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //3.添加搜索条件(已封装成方法)
        queryBuilder.withQuery(buildBasicQueryBuilder(searchRequest));

        //4.分页条件
        int page = searchRequest.getPage() - 1;   //pageable分页从0开始
        int size = searchRequest.getSize();
        queryBuilder.withPageable(PageRequest.of(page, size));

        //5.source过滤,控制显示字段数量
        queryBuilder.withSourceFilter(new FetchSourceFilter(
                new String[]{"id", "subTitle", "skus"}, null));

        //6.进行搜索
        AggregatedPage<Goods> goods = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //7.解析搜索结果
        long total = goods.getTotalElements();
        int totalPage = goods.getTotalPages();
        List<Goods> content = goods.getContent();

        List<GoodsDTO> goodsDTOS = BeanHelper.copyWithCollection(content, GoodsDTO.class);

        //8.封装结果并返回
        return new PageResult<GoodsDTO>(total, totalPage, goodsDTOS);
    }

    /**
     * 查询过滤项
     *
     * @param searchRequest
     * @return
     */
    @Override
    public Map<String, List<?>> queryFilters(SearchRequest searchRequest) {
        Map<String, List<?>> filterMap = new LinkedHashMap<>();

        //1.原生查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //2.添加查询条件
        QueryBuilder basicQueryBuilder = buildBasicQueryBuilder(searchRequest);
        queryBuilder.withQuery(basicQueryBuilder);

        //3.去除不需要的数据
        //3.1._source的include和exclude都设为null
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());
        //4.1.只显示第一页的第一条数据(因为至少为1,所以只显示1条)
        queryBuilder.withPageable(PageRequest.of(0, 1));

        //4.添加聚合
        //4.1.按分类category聚合
        //4.1.1.聚合名称
        String categoryAgg = "categoryAgg";
        //4.1.2.进行聚合
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        //4.2.按品牌brand聚合
        //4.2.1.聚合名称
        String brandAgg = "brandAgg";
        //4.2.2.进行聚合
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));

        //5.进行查询
        AggregatedPage<Goods> goodsAggregated = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //6.解析查询结果
        //6.1.获取所有的聚合结果
        Aggregations aggregations = goodsAggregated.getAggregations();
        //6.2.获取分类category的聚合结果,并进行处理
        Terms cTerms = aggregations.get(categoryAgg);
        List<Long> cidList = handleCategoryAgg(cTerms, filterMap);
        //6.3.获取品牌brand的聚合结果,并进行处理
        Terms bTerms = aggregations.get(brandAgg);
        handleBrandAgg(bTerms, filterMap);

        //7.如果cidList的长度为1,即选中了一个第三级分类,才进行查询specs的规格参数
        if (null != cidList && 1 == cidList.size()) {
            handleSpecsAgg(cidList.get(0), basicQueryBuilder, filterMap);
        }

        //7.返回处理结果
        return filterMap;
    }

    /**
     * 数据同步
     * 接收item上架的消息,新增索引
     * @param id
     */
    @Override
    public void createIndex(Long id) {
        //通过spu的id查询spu
        SpuDTO spuDTO = itemClient.querySpuById(id);
        //构建goods对象
        Goods goods = goodsService.buildGoods(spuDTO);
        //保存到索引库
        goodsRepository.save(goods);

    }

    /**
     * 数据同步
     * 接收item下架的消息,删除索引
     * @param id
     */
    @Override
    public void deleteIndex(Long id) {
        goodsRepository.deleteById(id);
    }

    /**
     * 对规格参数进行聚合查询,并处理specs规格参数的聚合结果
     *
     * @param cid               cid
     * @param basicQueryBuilder 基本查询条件
     * @param filterMap         返回结果
     */
    private void handleSpecsAgg(Long cid, QueryBuilder basicQueryBuilder, Map<String, List<?>> filterMap) {
        //根据分类查询规格参数specParam
        List<SpecParamDTO> specParam = itemClient.querySpecParams(null, cid, true);
        //1.原生查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //2.添加基本查询条件
        queryBuilder.withQuery(basicQueryBuilder);
        //3.去除不必要的查询结果
        queryBuilder.withPageable(PageRequest.of(0, 1));
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());

        //4.添加聚合
        for (SpecParamDTO param : specParam) {
            queryBuilder.addAggregation(AggregationBuilders.terms(param.getName()).field("specs." + param.getName()));
        }

        //5.进行查询
        AggregatedPage<Goods> result = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //6.解析聚合查询结果并进行封装
        //6.1.取出所有的聚合结果
        Aggregations aggregations = result.getAggregations();
        //6.2.遍历所有的聚合结果
        for (SpecParamDTO param : specParam) {
            //6.2.1.取出一个聚合结果
            Terms terms = aggregations.get(param.getName());
            List<String> paramValues = terms.getBuckets().stream()
                    .map(bucket -> bucket.getKeyAsString())
                    .filter(str -> StringUtils.isNotBlank(str))
                    .collect(Collectors.toList());
            filterMap.put(param.getName(), paramValues);
        }
    }

    /**
     * 处理品牌brand的聚合结果
     *
     * @param bTerms
     * @param filterMap
     */
    private void handleBrandAgg(Terms bTerms, Map<String, List<?>> filterMap) {
        //获取聚合后的所有的brandId,并放进一个集合中
        ArrayList<Long> ids = new ArrayList<>();
        bTerms.getBuckets().stream().forEach(bucket -> {
            long id = bucket.getKeyAsNumber().longValue();
            ids.add(id);
        });
        //根据brandId查询品牌名称
        List<BrandDTO> BrandDTOS = itemClient.queryBrandByIds(ids);
        //将数据放进map集合中
        filterMap.put("品牌", BrandDTOS);
    }

    /**
     * 处理分类category的聚合结果
     *
     * @param cTerms
     * @param filterMap
     */
    private List<Long> handleCategoryAgg(Terms cTerms, Map<String, List<?>> filterMap) {
        List<Long> ids = new ArrayList<>();
        cTerms.getBuckets().stream().forEach(bucket -> {
            long cid = bucket.getKeyAsNumber().longValue();
            ids.add(cid);
        });
        List<CategoryDTO> categoryDTOS = itemClient.queryCategoryByIds(ids);
        filterMap.put("分类", categoryDTOS);
        return ids;
    }

    /**
     * 添加基本查询条件
     *
     * @param searchRequest
     * @return
     */
    /*private QueryBuilder buildBasicQueryBuilder(SearchRequest searchRequest) {
        return QueryBuilders.matchQuery("all", searchRequest.getKey()).operator(Operator.AND);
    }*/

    /**
     * 添加基本查询条件
     * 有过滤项
     *
     * @param searchRequest
     * @return
     */
    private QueryBuilder buildBasicQueryBuilder(SearchRequest searchRequest) {
        //构建布尔查询,组装其他条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //构建基本查询条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", searchRequest.getKey()).operator(Operator.AND));

        //添加过滤项查询条件
        Map<String, String> filters = searchRequest.getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            Set<Map.Entry<String, String>> entries = filters.entrySet();
            for (Map.Entry<String, String> entry : entries) {
                String key = entry.getKey();
                if ("分类".equals(key)) {
                    key = "categoryId";
                } else if ("品牌".equals(key)) {
                    key = "brandId";
                } else {
                    key = "spec." + key;
                }
                boolQueryBuilder.filter(QueryBuilders.termQuery(key, entry.getValue()));
            }
        }

        return boolQueryBuilder;
    }
}
